#include "TinkerCell3D.h"
#include <maya/MTemplateCommand.h>
#include <maya/MMessage.h>
#include <maya/MGlobal.h>
#include <maya/MTime.h>
#include <maya/MDGMessage.h>
#include <maya/MDoubleArray.h>
#include <maya/MpxCommand.h>
#include "SBMLImportExport.h"

using namespace Tinkercell;
using namespace nTinker;

MainWindow * Animator::mainWindow = 0;
Plot2DWidget * Animator::plotWidget = 0;
Tinkercell::SBMLImportExport * Animator::sbmlTool = 0;
SBML_sim * Animator::simulator = 0;
QHash<QString, int> SetParamCmd::nameToIndex;
std::vector<double> SetParamCmd::paramValues;
std::vector<double> SimulateCmd::varValues;
QStringList cellNames;

void nTinker::updateEmittersPy()
{
	double dt = 1.0;
	QString command;
	command += QString("def updateEmitters():\n");
	command += QString("    y = cmds.simulate(");
	command += QString::number(dt);
	command += QString(")\n");
	for (int i=0; i < cellNames.size(); ++i)
	{
		command += QString("    cmds.setAttr(\"");
		command += cellNames[i];
		command += QString("Emitter.rate\",");
		command += QString("10.0/(1+y[");
		command += QString::number(i);
		command += QString("]))\n");
	}
	MGlobal::displayInfo(MString(command.toAscii().data()));
	MGlobal::executePythonCommand( MString(command.toAscii().data()) );
}

void nTinker::updateCountsPy()
{
	QString command;
	command += QString("def updateCounts():\n");
	for (int i=0; i < cellNames.size(); ++i)
	{
		command += QString("    xs = cmds.getAttr(\"");
		command += cellNames[i];
		command += QString("ParticlesShape.position\")\n\
    d = cmds.computeParticleDensity([0,0,0],[2,2,2],xs)\n\
    cmds.setParam(\"");
		command += cellNames[i];
		command += QString("_count\", d)\n");
	}
	MGlobal::displayInfo(MString(command.toAscii().data()));
	MGlobal::executePythonCommand( MString(command.toAscii().data()) );
}

double nTinker::valueDistance(const nTinker::Centroid& a, const nTinker::Centroid& b)
{
	double sumsq = 0.0;
	for (int i=0; i < a.values.length() && i < b.values.length(); ++i)
		sumsq += pow(a.values[i] - b.values[i], 2);
	return sqrt(sumsq);
}

double nTinker::posDistance(const nTinker::Centroid& a, const nTinker::Centroid& b)
{
	return a.pos.distanceTo(b.pos);
}

MStatus SetParamCmd::doIt(const MArgList& args)
{
	MStatus status1, status2;
	unsigned int i;

	i = 0;
	MString name = args.asString(i, &status1);

	i = 1;
	double value = args.asDouble(i, &status2);

	if (Animator::simulator)
	{
		if (paramValues.size() < 1)
			paramValues = Animator::simulator->getParameterValues();

		QString s = QString(name.asChar());
		if (nameToIndex.contains(s))
		{
			i = nameToIndex[s];
			paramValues[i] = value;
		}
		else
		{
			std::vector< std::string > names = Animator::simulator->getParameterNames();
			for (i=0; i < names.size(); ++i)
				if (QString(names[i].c_str()) == s)
				{
					nameToIndex[s] = i;
					paramValues[i] = value;
				}
		}
		Animator::simulator->setParameters(paramValues);
	}

	if (!status1.error() && !status2.error())
	{
		setResult(true);
	}

	setResult(false);
	return MS::kSuccess;
}

MStatus ComputeDensityCmd::doIt(const MArgList& args)
{
	MStatus status;
	int n = 0;
	unsigned int i;
	bool error = false;
	
	i = 0;
	MDoubleArray pos1 = args.asDoubleArray(i,&status);
	if (status.error())
		error = true;

	i = 1;
	MDoubleArray pos2 = args.asDoubleArray(i,&status);
	if (status.error())
		error = true;

	i = 2;
	MDoubleArray m = args.asDoubleArray(i,&status);
	MGlobal::displayInfo( MString( QString::number(m.length()).toAscii().data() ) );
	if (status.error())
		error = true;

	if (error && pos1.length() == 3 && pos2.length() == 3)
	{
		for (i=0; (i+2) < m.length(); i+=3)
			if (m[i] > pos1[0] && m[i+1] < pos2[0] &&
				m[i+1] > pos1[1] && m[i+1] < pos2[1] &&
				m[i+2] > pos1[1] && m[i+2] < pos2[2])
				++n;
	}

	setResult(n);
	return MS::kSuccess;
}

MStatus SimulateCmd::doIt(const MArgList& args)
{
	MStatus status;
	unsigned int i = 0, i2, j;
	double time = args.asDouble(i, &status);
	MDoubleArray results;
	MainWindow * mainWindow = Animator::mainWindow;
	Plot2DWidget * plotWidget = Animator::plotWidget;
	SBMLImportExport * sbmlTool = Animator::sbmlTool;
	SBML_sim * sim = Animator::simulator;

	if (sim == 0)
		Animator::simulator = sim = new SBML_sim("C:\\Users\\Deepak\\Desktop\\temp.txt");

	if (!status.error())
	{
		NumericalDataTable plotData;
		if (plotWidget && plotWidget->data())
			plotData = *(plotWidget->data());

		if (varValues.size() > 0)
			sim->setVariableValues(varValues);

		std::vector< std::vector<double> > m = sim->simulate(time,time/20.0);
		int rows = m[0].size(), cols = m.size();
		
		results.setLength(cols - 1);

		if (varValues.size() < (cols-1))
			varValues.resize(cols - 1);
		
		for (i=1; i < cols; ++i)
		{
			results[i-1] = m[i][rows-1];
			varValues[i-1] = m[i][rows-1];
		}
		
		i2 = plotData.rows();
		plotData.resize(i2 + rows, cols);
		std::vector<std::string> names = sim->getVariableNames();
		for (j=0; j < cols && j < names.size(); ++j)
			plotData.setColumnName(j+1, names[j].c_str() );

		plotData.setColumnName(0, "time");
		for (j=1; j < cols; ++j)
			for (i=0; i < rows; ++i)
				plotData.value(i2 + i, j) = m[j][i];

		if (i2 > 0)
			time = plotData.value(i2-1, 0);
		else
			time = 0;

		for (i=0; i < rows; ++i)
			plotData.value(i2 + i, 0) = m[0][i] + time;

		plotData.description() = QString("time course");
		if (plotWidget)
		{
			if (i2 > 0)
				plotWidget->updateData(plotData,"time course");
			else
				plotWidget->plot(plotData,"time course");
		}
	}

	setResult(results);
	return MS::kSuccess;
}

void animation_callback( MTime& time, void * clientData )
{
	//MSelectionList sList;
    //MString toMatch ("bac10Particles");
    //MGlobal::getSelectionListByName(toMatch, sList);
	if (time.as(MTime::kSeconds) < 1 && Animator::plotWidget && Animator::plotWidget->data())
	{
		Animator::plotWidget->data()->resize(0,0);
	}
	MString s("updateCounts();\nupdateEmitters();");
	MGlobal::executePythonCommand( s );
}

Animator * Animator::load()
{
	GlobalSettings::ENABLE_HISTORY_WINDOW = false;
	GlobalSettings::ENABLE_CONSOLE_WINDOW = false;
	GlobalSettings::ENABLE_GRAPHING_TOOLS = false;
	GlobalSettings::ENABLE_CODING_TOOLS = false;
	GlobalSettings::ENABLE_ALIGNMENT_MENU = false;
	GlobalSettings::ENABLE_ZOOM_TOOLBAR = false;
	GlobalSettings::ENABLE_COLOR_TOOLBAR = false;
	GlobalSettings::ENABLE_TEXT_TOOLBAR = false;
	GlobalSettings::ENABLE_PYTHON = false;
	GlobalSettings::ENABLE_RUBY = false;
	GlobalSettings::ENABLE_OCTAVE = false;
	GlobalSettings::ENABLE_LOADSAVE_TOOL = true;
	GraphicsScene::USE_DEFAULT_BEHAVIOR = false;

	Animator::mainWindow = new MainWindow(new MainWindow(true,false,false));
	GraphicsScene * scene = mainWindow->newScene();
	Animator::plotWidget = new Plot2DWidget;
	mainWindow->addTool(Animator::sbmlTool = new SBMLImportExport);

	QSplitter * splitter = new QSplitter;
	splitter->setOrientation(Qt::Vertical);
	splitter->addWidget(Animator::mainWindow);
	splitter->addWidget(Animator::plotWidget);
	widget = splitter;

	Animator * animator = new Animator(Animator::mainWindow);
	mainWindow->toolBarForTools->addAction(QIcon(":/images/play.png"),"nTinker animation", animator, SLOT(animate()));
	splitter->show();

	return animator;
}

void Animator::unload()
{
	if (widget)
		widget->close();
	
	if (simulator)
		delete simulator;

}

Animator::Animator(QObject * parent) :  QObject(parent)
{
	QString appDir = QCoreApplication::applicationDirPath();
	Ontology::readNodes(appDir + tr("/NodesTree.nt"),"ntriples");
	Ontology::readConnections(appDir + tr("/ConnectionsTree.nt"),"ntriples");
}

void Animator::animate()
{
	createParticlesFromCells();
	updateEmittersPy();
	updateCountsPy();
	MDGMessage::addTimeChangeCallback(animation_callback);

	MString command("cmds.playbackOptions(min=0,max=3000, loop=\"once\")\n\
cmds.currentTime(0)\n\
cmds.play(wait)\n\"");
	MGlobal::executePythonCommand(command);
}

void Animator::createParticlesFromCells()
{
	if (!mainWindow || !mainWindow->currentNetwork()) return;

	cellNames.clear();
	QList<ItemHandle*> handles = mainWindow->currentNetwork()->handles();
	for (int i=0; i < handles.size(); ++i)
	{
		if (handles[i]->isA("Cell") && handles[i]->hasNumericalData("position"))
		{
			cellNames += handles[i]->fullName("_");
			MString name( handles[i]->fullName("_").toAscii().data() );
			double x = handles[i]->numericalData("position","x")/1600.0;
			double y = handles[i]->numericalData("position","y")/1600.0;
			MPoint point(x,y);

			QList<NodeGraphicsItem*> nodes = NodeGraphicsItem::cast(handles[i]->graphicsItems);

			QColor color(0,255,0);
			if (!nodes.isEmpty() && nodes[0]->longestShape())
			{
				QBrush brush = nodes[0]->longestShape()->defaultBrush;
				if (brush.gradient())
				{
					QGradientStops stops = brush.gradient()->stops();
					color = stops.last().second;
				}
				else
				{
					color = brush.color();
				}
			}
			MGlobal::displayInfo(MString(
				(QString::number(color.redF()) + QString(" ") + 
				QString::number(color.greenF()) + QString(" ") + 
				QString::number(color.blueF())).toAscii().data()));
			createParticlesAt(name,point, color);
		}
	}
}

void Animator::createParticlesAt(MString name, MPoint point, QColor color)
{
	MString	x = MString(QString::number(point.x).toAscii().data()), 
					y = MString(QString::number(point.y).toAscii().data()),
					z = MString(QString::number(point.z).toAscii().data()),
					r = MString(QString::number(color.redF()).toAscii().data()),
					g = MString(QString::number(color.greenF()).toAscii().data()),
					b = MString(QString::number(color.blueF()).toAscii().data());

	MString command("import maya.cmds as cmds\n\
myX = 0.0;\n\
from random import *\n\
n = 1\n\
for i in range(0,n):\n\
    x = 2 * (random() - 0.5) * ");

	command += x;
	command += MString("\n\
    y = 2 * (random() - 0.5) * ");

	command += y;
	command += MString("\n\
    z = 2 * (random() - 0.5) * ");

	command += z;
	command += MString("\n\
    pname = \"");
	command += name;
	command += MString("\"\n\
    cmds.particle(position=[x,y,z], name=pname)\n\
    emname = pname + \"Emitter\"\n\
    cmds.emitter([pname], name=emname, type=\"omni\", rate = 1.0, speed = 0.5)\n\
    cmds.nParticle(pname, name=(pname + \"Particles\"),shapeName=(pname+\"ParticlesShape\"))\n\
    cmds.setAttr(pname+\".visibility\",0)\n\
    cmds.setAttr(pname+\"ParticlesShape.particleRenderType\",4)\n\
    cmds.setAttr(pname+\"ParticlesShape.ignoreSolverGravity\",1)\n\
    cmds.setAttr(pname+\"ParticlesShape.lifespan\",10)\n\
    cmds.connectDynamic((pname + \"Particles\") , emitters=emname)\n\
    shapename = pname + \"ParticlesShape\"\n\
    cmds.setAttr(shapename + \".color[0].color_Color\", ");

	command += r;
	command += MString(",");
	command += g;
	command += MString(",");
	command += b;
	command += MString(", type=\"double3\")\n\
    cmds.setAttr(shapename + \".color[1].color_Color\", ");
	
	command += r;
	command += MString(",");
	command += g;
	command += MString(",");
	command += b;
	command += MString(", type=\"double3\")\n\
    cmds.setAttr(shapename + \".selfCollide\", 1.0)\n\
    cmds.setAttr(shapename + \".radiusScale[0].radiusScale_Position\", 0.0)\n\
    cmds.setAttr(shapename + \".radiusScale[0].radiusScale_FloatValue\", 0.2)\n\
    cmds.setAttr(shapename + \".radiusScale[1].radiusScale_Position\", 0.8)\n\
    cmds.setAttr(shapename + \".radiusScale[1].radiusScale_FloatValue\", 2.0)\n\
    cmds.setAttr(shapename + \".radiusScale[2].radiusScale_Position\", 1.0)\n\
    cmds.setAttr(shapename + \".radiusScale[2].radiusScale_FloatValue\", 0.2)\n\
    cmds.setAttr(shapename + \".radiusScale[0].radiusScale_Interp\", 1)\n\
    cmds.setAttr(shapename + \".radiusScale[1].radiusScale_Interp\", 1)\n\
    cmds.setAttr(shapename + \".radiusScale[2].radiusScale_Interp\", 1)\n\
    cmds.setAttr(shapename + \".radiusScaleInputMax\", 10.0)\n\
    cmds.setAttr(shapename + \".radiusScaleInput\", 1.0)\n\
nucname = \"nucleus1\"\n\
cmds.setAttr(nucname + \".visibility\", 0)\n\
cmds.setAttr(nucname + \".gravity\", 0.0)\n\
cmds.setAttr(nucname + \".airDensity\", 1.0)\n\
cmds.playbackOptions(min=0,max=100, loop=\"once\")\n\
cmds.currentTime(0)\n\
cmds.play(wait=True)\n\
for i in range(0,n):\n\
    pname = \"");
	command += name;
	command += MString("\" + \"Particles\"\n\
    emname = pname + \"Emitter\"\n\
    cmds.emitter([pname], name=emname, type=\"omni\", rate = 1.0, speed = 0.5)\n\
    pname2 = \"");
	command += name;
	command += MString("\" + \"Child\" + str(i)\n\
    cmds.nParticle(pname, name=(pname2 + \"Particles\"),shapeName=(pname2+\"ParticlesShape\"))\n\
    cmds.setAttr(emname + \".visibility\", 0)\n\
    cmds.setAttr(pname2+\"ParticlesShape.particleRenderType\",4)\n\
    cmds.setAttr(pname2+\"ParticlesShape.ignoreSolverGravity\",1)\n\
    cmds.setAttr(pname2+\"ParticlesShape.lifespan\",3)\n\
    cmds.connectDynamic((pname2 + \"Particles\") , emitters=emname)\n\
    shapename = pname2 + \"ParticlesShape\"\n\
    cmds.setAttr(shapename + \".color[0].color_Color\",");
	
	command += r;
	command += MString(",");
	command += g;
	command += MString(",");
	command += b;	
	command += MString(",type=\"double3\")\n\
    cmds.setAttr(shapename + \".color[1].color_Color\", ");
	
	command += r;
	command += MString(",");
	command += g;
	command += MString(",");
	command += b;	
	command += MString(", type=\"double3\")\n\
    cmds.setAttr(shapename + \".selfCollide\", 1.0)\n\
    cmds.setAttr(shapename + \".radiusScale[0].radiusScale_Position\", 0.0)\n\
    cmds.setAttr(shapename + \".radiusScale[0].radiusScale_FloatValue\", 0.2)\n\
    cmds.setAttr(shapename + \".radiusScale[1].radiusScale_Position\", 0.8)\n\
    cmds.setAttr(shapename + \".radiusScale[1].radiusScale_FloatValue\", 2.0)\n\
    cmds.setAttr(shapename + \".radiusScale[2].radiusScale_Position\", 1.0)\n\
    cmds.setAttr(shapename + \".radiusScale[2].radiusScale_FloatValue\", 0.2)\n\
    cmds.setAttr(shapename + \".radiusScale[0].radiusScale_Interp\", 1)\n\
    cmds.setAttr(shapename + \".radiusScale[1].radiusScale_Interp\", 1)\n\
    cmds.setAttr(shapename + \".radiusScale[2].radiusScale_Interp\", 1)\n\
    cmds.setAttr(shapename + \".radiusScaleInputMax\", 10.0)\n\
    cmds.setAttr(shapename + \".radiusScaleInput\", 1.0)\n\
cmds.setAttr(nucname + \".airDensity\", 1.0)\n\
cmds.playbackOptions(min=0,max=2000, loop=\"once\")");
	/*\n\
cmds.currentTime(0)\n\
cmds.play()\n");*/
	MGlobal::executePythonCommand(command);
}

QWidget * Animator::widget = 0;